climaybe 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -115,15 +115,21 @@ npx climaybe ensure-branches
115
115
  git push origin --all
116
116
  ```
117
117
 
118
- ### `climaybe update-workflows` / `climaybe theme update-workflows`
118
+ ### `climaybe update` / `climaybe theme update`
119
119
 
120
- Refresh GitHub Actions workflows from the latest bundled templates.
120
+ Refresh all climaybe-managed project files from your installed CLI version:
121
+ - GitHub workflows
122
+ - root dev-kit files (`.theme-check.yml`, `.shopifyignore`, `.prettierrc`, `.lighthouserc.js`, `.gitignore`)
123
+ - `package.json` managed deps (`climaybe`, `tailwindcss`)
124
+ - optional `.vscode/tasks.json` (if enabled)
125
+ - optional commitlint + Husky files (if enabled)
126
+ - optional Cursor bundle files (if enabled)
121
127
 
122
128
  ```bash
123
- npx climaybe update-workflows
129
+ npx climaybe update
124
130
  ```
125
131
 
126
- Useful after updating the CLI to get the latest workflow improvements.
132
+ `update-workflows` still works as a backward-compatible alias.
127
133
 
128
134
  ### `climaybe setup-commitlint`
129
135
 
@@ -141,7 +147,7 @@ Install Electric Maybe **Cursor rules, skills, and subagents** into `.cursor/rul
141
147
  npx climaybe add-cursor
142
148
  ```
143
149
 
144
- The previous command name `add-cursor-skill` still works as an alias. Re-running replaces the bundled rules, skills, and subagent files with the copies shipped by your installed climaybe version (same idea as `update-workflows`).
150
+ The previous command name `add-cursor-skill` still works as an alias. Re-running replaces the bundled rules, skills, and subagent files with the copies shipped by your installed climaybe version (same idea as `update`).
145
151
 
146
152
  ## Configuration
147
153
 
package/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 3.0.2
1
+ 3.0.4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
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": {
@@ -1,10 +1,20 @@
1
1
  import pc from 'picocolors';
2
- import { getMode, isBuildWorkflowsEnabled, isPreviewWorkflowsEnabled, readConfig } from '../lib/config.js';
2
+ import {
3
+ getMode,
4
+ isBuildWorkflowsEnabled,
5
+ isCommitlintEnabled,
6
+ isCursorSkillsEnabled,
7
+ isPreviewWorkflowsEnabled,
8
+ readConfig,
9
+ } from '../lib/config.js';
3
10
  import { scaffoldWorkflows } from '../lib/workflows.js';
4
11
  import { requireThemeProject } from '../lib/theme-guard.js';
12
+ import { scaffoldThemeDevKit } from '../lib/theme-dev-kit.js';
13
+ import { scaffoldCommitlint } from '../lib/commit-tooling.js';
14
+ import { scaffoldCursorBundle } from '../lib/cursor-bundle.js';
5
15
 
6
- export async function updateWorkflowsCommand() {
7
- console.log(pc.bold('\n climaybe — Update Workflows\n'));
16
+ export async function updateCommand() {
17
+ console.log(pc.bold('\n climaybe — Update\n'));
8
18
 
9
19
  if (!requireThemeProject()) return;
10
20
 
@@ -17,7 +27,24 @@ export async function updateWorkflowsCommand() {
17
27
  const mode = getMode();
18
28
  const includePreview = isPreviewWorkflowsEnabled();
19
29
  const includeBuild = isBuildWorkflowsEnabled();
30
+
31
+ // Keep theme project files in sync (root files, package.json, .gitignore, VS Code tasks).
32
+ scaffoldThemeDevKit({
33
+ includeVSCodeTasks: !!config.vscode_tasks,
34
+ defaultStoreDomain: config.default_store || '',
35
+ });
36
+
37
+ if (isCommitlintEnabled()) {
38
+ scaffoldCommitlint(process.cwd(), { skipInstall: true });
39
+ }
40
+ if (isCursorSkillsEnabled()) {
41
+ scaffoldCursorBundle();
42
+ }
43
+
20
44
  scaffoldWorkflows(mode, { includePreview, includeBuild });
21
45
 
22
- console.log(pc.bold(pc.green('\n Workflows updated!\n')));
46
+ console.log(pc.bold(pc.green('\n Project files updated!\n')));
23
47
  }
48
+
49
+ // Backward-compatible export for old command name.
50
+ export const updateWorkflowsCommand = updateCommand;
package/src/index.js CHANGED
@@ -3,7 +3,7 @@ import { initCommand, reinitCommand } from './commands/init.js';
3
3
  import { addStoreCommand } from './commands/add-store.js';
4
4
  import { switchCommand } from './commands/switch.js';
5
5
  import { syncCommand } from './commands/sync.js';
6
- import { updateWorkflowsCommand } from './commands/update-workflows.js';
6
+ import { updateCommand } 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';
@@ -81,9 +81,10 @@ function registerThemeCommands(cmd) {
81
81
  .action(createEntrypointsCommand);
82
82
 
83
83
  cmd
84
- .command('update-workflows')
85
- .description('Refresh GitHub Actions workflows from latest bundled templates')
86
- .action(updateWorkflowsCommand);
84
+ .command('update')
85
+ .alias('update-workflows')
86
+ .description('Refresh workflows and all climaybe-managed project files')
87
+ .action(updateCommand);
87
88
 
88
89
  cmd
89
90
  .command('ensure-branches')
@@ -1,12 +1,33 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { watchTree } from './watch.js';
4
- import { join } from 'node:path';
4
+ import { isAbsolute, join, relative } from 'node:path';
5
5
  import pc from 'picocolors';
6
6
  import { readConfig } from './config.js';
7
7
  import { buildScripts } from './build-scripts.js';
8
8
  import { runShopify } from './shopify-cli.js';
9
9
 
10
+ function tagLabel(tag, color = (s) => s) {
11
+ return color(`[${tag}]`);
12
+ }
13
+
14
+ function writeTaggedLine(tag, color, line, stream = process.stdout) {
15
+ const text = String(line || '').trimEnd();
16
+ if (!text) return;
17
+ stream.write(`${tagLabel(tag, color)} ${text}\n`);
18
+ }
19
+
20
+ function writeTaggedChunk(tag, color, chunk, stream = process.stdout) {
21
+ for (const line of String(chunk || '').split('\n')) {
22
+ if (!line) continue;
23
+ writeTaggedLine(tag, color, line, stream);
24
+ }
25
+ }
26
+
27
+ function printServeStartupHeader() {
28
+ console.log(pc.bold('\n climaybe — serve startup\n'));
29
+ }
30
+
10
31
  function getPackageDir() {
11
32
  return process.env.CLIMAYBE_PACKAGE_DIR || process.cwd();
12
33
  }
@@ -16,21 +37,29 @@ function binPath(binName) {
16
37
  return join(getPackageDir(), 'node_modules', '.bin', binName);
17
38
  }
18
39
 
19
- function spawnLogged(command, args, { name, cwd = process.cwd(), env = process.env } = {}) {
40
+ function spawnLogged(
41
+ command,
42
+ args,
43
+ { name, cwd = process.cwd(), env = process.env, stdio = 'inherit', tag = null, color = (s) => s } = {}
44
+ ) {
20
45
  const child = spawn(command, args, {
21
46
  cwd,
22
47
  env,
23
- stdio: 'inherit',
48
+ stdio,
24
49
  shell: process.platform === 'win32',
25
50
  });
51
+ if (stdio === 'pipe') {
52
+ if (child.stdout) child.stdout.on('data', (buf) => writeTaggedChunk(tag || name, color, String(buf), process.stdout));
53
+ if (child.stderr) child.stderr.on('data', (buf) => writeTaggedChunk(tag || name, color, String(buf), process.stderr));
54
+ }
26
55
 
27
56
  child.on('exit', (code, signal) => {
28
57
  if (signal) {
29
- console.log(pc.yellow(`\n ${name} exited with signal ${signal}\n`));
58
+ writeTaggedLine(tag || name, color, `exited with signal ${signal}`);
30
59
  return;
31
60
  }
32
61
  if (code && code !== 0) {
33
- console.log(pc.red(`\n ${name} exited with code ${code}\n`));
62
+ writeTaggedLine(tag || name, color, `exited with code ${code}`, process.stderr);
34
63
  }
35
64
  });
36
65
 
@@ -41,7 +70,7 @@ function runTailwind(args, { cwd = process.cwd(), env = process.env, name = 'tai
41
70
  return spawnLogged(
42
71
  'npx',
43
72
  ['-y', '--package', '@tailwindcss/cli@latest', '--package', 'tailwindcss@latest', 'tailwindcss', ...args],
44
- { name, cwd, env }
73
+ { name, cwd, env, stdio: 'pipe', tag: 'tailwind', color: pc.blue }
45
74
  );
46
75
  }
47
76
 
@@ -63,20 +92,143 @@ function safeKill(child) {
63
92
  }
64
93
  }
65
94
 
95
+ function writeThemeCheckErrorsOnly(chunk, stream = process.stdout) {
96
+ for (const line of String(chunk || '').split('\n')) {
97
+ if (!line) continue;
98
+ if (/warning/i.test(line)) continue;
99
+ writeTaggedLine('theme-check', pc.red, line, stream);
100
+ }
101
+ }
102
+
103
+ function collectThemeCheckOffenses(payload) {
104
+ const out = [];
105
+ const visit = (value, inheritedPath = '') => {
106
+ if (!value) return;
107
+ if (Array.isArray(value)) {
108
+ for (const item of value) visit(item, inheritedPath);
109
+ return;
110
+ }
111
+ if (typeof value !== 'object') return;
112
+
113
+ const localPath =
114
+ value.path ||
115
+ value.file ||
116
+ value.file_path ||
117
+ value.relative_path ||
118
+ value.source ||
119
+ inheritedPath ||
120
+ '';
121
+
122
+ if (typeof value.severity === 'string' && typeof value.message === 'string') {
123
+ out.push({ ...value, __path: localPath || inheritedPath || '' });
124
+ return;
125
+ }
126
+
127
+ if (Array.isArray(value.offenses)) {
128
+ for (const offense of value.offenses) visit(offense, localPath || inheritedPath);
129
+ }
130
+ if (Array.isArray(value.checks)) {
131
+ for (const check of value.checks) visit(check, localPath || inheritedPath);
132
+ }
133
+
134
+ for (const nested of Object.values(value)) visit(nested, localPath || inheritedPath);
135
+ };
136
+ visit(payload);
137
+ return out;
138
+ }
139
+
140
+ function normalizeThemeCheckPath(file, cwd) {
141
+ const raw = String(file || '').trim();
142
+ if (!raw) return 'unknown';
143
+ if (isAbsolute(raw)) {
144
+ const rel = relative(cwd, raw);
145
+ if (rel && !rel.startsWith('..')) return rel;
146
+ }
147
+ return raw;
148
+ }
149
+
150
+ function formatThemeCheckError(offense, { cwd = process.cwd() } = {}) {
151
+ const file = offense.__path || offense.path || offense.file || offense.file_path || offense.relative_path || 'unknown';
152
+ const line =
153
+ offense.start_line ||
154
+ offense.startLine ||
155
+ offense.line ||
156
+ offense.line_number ||
157
+ offense.start_row ||
158
+ offense.row ||
159
+ '?';
160
+ const check = offense.check || offense.check_name || 'theme-check';
161
+ const message = offense.message || '';
162
+ const safeFile = normalizeThemeCheckPath(file, cwd);
163
+ return `${pc.cyan(safeFile)}:${pc.yellow(String(line))} ${pc.magenta(`[${check}]`)} ${pc.white(message)}`;
164
+ }
165
+
166
+ function runThemeCheckFiltered({ cwd = process.cwd() } = {}) {
167
+ let stdout = '';
168
+ let stderr = '';
169
+ const child = runShopify(['theme', 'check', '--fail-level', 'error', '--output', 'json'], {
170
+ cwd,
171
+ name: 'theme-check',
172
+ stdio: 'pipe',
173
+ onStdout: (chunk) => {
174
+ stdout += String(chunk || '');
175
+ },
176
+ onStderr: (chunk) => {
177
+ stderr += String(chunk || '');
178
+ },
179
+ });
180
+ child.on('exit', () => {
181
+ const trimmed = stdout.trim();
182
+ if (trimmed) {
183
+ try {
184
+ const parsed = JSON.parse(trimmed);
185
+ const offenses = collectThemeCheckOffenses(parsed).filter(
186
+ (offense) => String(offense.severity || '').toLowerCase() === 'error'
187
+ );
188
+ const seen = new Set();
189
+ for (const offense of offenses) {
190
+ const key = JSON.stringify([
191
+ offense.__path || offense.path || offense.file || offense.file_path || offense.relative_path || '',
192
+ offense.start_line || offense.startLine || offense.line || offense.line_number || offense.start_row || offense.row || '',
193
+ offense.check || offense.check_name || '',
194
+ offense.message || '',
195
+ ]);
196
+ if (seen.has(key)) continue;
197
+ seen.add(key);
198
+ writeTaggedLine('theme-check', pc.red, formatThemeCheckError(offense, { cwd }), process.stderr);
199
+ }
200
+ } catch {
201
+ writeThemeCheckErrorsOnly(stdout, process.stdout);
202
+ }
203
+ }
204
+ if (stderr.trim()) {
205
+ writeThemeCheckErrorsOnly(stderr, process.stderr);
206
+ }
207
+ });
208
+ return child;
209
+ }
210
+
66
211
  export function serveShopify({ cwd = process.cwd() } = {}) {
67
212
  const config = readConfig(cwd) || {};
68
213
  const store = config.default_store || config.store || '';
69
214
  const args = ['theme', 'dev', '--theme-editor-sync'];
70
215
  if (store) args.push(`--store=${store}`);
216
+ // Keep Shopify on inherited stdio so reconciliation prompts remain interactive.
71
217
  return runShopify(args, { cwd, name: 'shopify' });
72
218
  }
73
219
 
74
220
  export function serveAssets({ cwd = process.cwd(), includeThemeCheck = true } = {}) {
221
+ printServeStartupHeader();
75
222
  const env = { ...process.env, NODE_ENV: 'production' };
76
223
  const styleEntrypoint = join(cwd, '_styles', 'main.css');
77
224
  const tailwind = existsSync(styleEntrypoint)
78
225
  ? runTailwind(['-i', '_styles/main.css', '-o', 'assets/style.css', '--watch'], { cwd, env, name: 'tailwind' })
79
226
  : null;
227
+ if (!tailwind) {
228
+ writeTaggedLine('tailwind', pc.blue, 'skipped (missing _styles/main.css)');
229
+ } else {
230
+ writeTaggedLine('tailwind', pc.blue, 'watching _styles/main.css -> assets/style.css');
231
+ }
80
232
 
81
233
  // Optional dev MCP (non-blocking if missing)
82
234
  const devMcp = spawnLogged('npx', ['-y', '@shopify/dev-mcp@latest'], { name: 'dev-mcp', cwd });
@@ -85,10 +237,12 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = true } =
85
237
  if (existsSync(scriptsDir)) {
86
238
  try {
87
239
  buildScripts({ cwd });
88
- console.log(pc.green(' scripts built (initial)'));
240
+ writeTaggedLine('scripts', pc.yellow, 'built (initial)');
89
241
  } catch (err) {
90
- console.log(pc.red(` initial scripts build failed: ${err.message}`));
242
+ writeTaggedLine('scripts', pc.yellow, `initial build failed: ${err.message}`, process.stderr);
91
243
  }
244
+ } else {
245
+ writeTaggedLine('scripts', pc.yellow, 'skipped (missing _scripts/)');
92
246
  }
93
247
  const scriptsWatch = existsSync(scriptsDir)
94
248
  ? watchTree({
@@ -98,9 +252,9 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = true } =
98
252
  onChange: () => {
99
253
  try {
100
254
  buildScripts({ cwd });
101
- console.log(pc.green(' scripts rebuilt'));
255
+ writeTaggedLine('scripts', pc.yellow, 'rebuilt');
102
256
  } catch (err) {
103
- console.log(pc.red(` scripts build failed: ${err.message}`));
257
+ writeTaggedLine('scripts', pc.yellow, `build failed: ${err.message}`, process.stderr);
104
258
  }
105
259
  },
106
260
  })
@@ -114,7 +268,7 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = true } =
114
268
  return;
115
269
  }
116
270
  themeCheckRunning = true;
117
- const child = runShopify(['theme', 'check'], { cwd, name: 'theme-check' });
271
+ const child = runThemeCheckFiltered({ cwd });
118
272
  child.on('exit', () => {
119
273
  themeCheckRunning = false;
120
274
  if (themeCheckQueued) {
@@ -142,7 +296,10 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = true } =
142
296
  : null;
143
297
 
144
298
  if (includeThemeCheck) {
299
+ writeTaggedLine('theme-check', pc.red, 'running initial scan (errors only)');
145
300
  runThemeCheck();
301
+ } else {
302
+ writeTaggedLine('theme-check', pc.red, 'disabled');
146
303
  }
147
304
 
148
305
  const cleanup = () => {
@@ -156,12 +313,21 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = true } =
156
313
  }
157
314
 
158
315
  export function serveAll({ cwd = process.cwd(), includeThemeCheck = true } = {}) {
159
- // Keep Shopify CLI in the foreground (real TTY), and run watchers in background.
316
+ // Start assets first, then bring up Shopify after a short delay.
160
317
  const assets = serveAssets({ cwd, includeThemeCheck });
161
- const shopify = serveShopify({ cwd });
318
+ let shopify = null;
319
+ const shopifyStartDelayMs = 2500;
320
+ const shopifyTimer = setTimeout(() => {
321
+ shopify = serveShopify({ cwd });
322
+ shopify.on('exit', () => {
323
+ cleanup();
324
+ });
325
+ }, shopifyStartDelayMs);
326
+ console.log(pc.dim(` Waiting ${shopifyStartDelayMs}ms before starting Shopify...`));
162
327
 
163
328
  const cleanup = () => {
164
- assets.cleanup?.();
329
+ clearTimeout(shopifyTimer);
330
+ assets?.cleanup?.();
165
331
  safeKill(shopify);
166
332
  };
167
333
 
@@ -169,11 +335,7 @@ export function serveAll({ cwd = process.cwd(), includeThemeCheck = true } = {})
169
335
  process.once('SIGINT', handleSignal);
170
336
  process.once('SIGTERM', handleSignal);
171
337
 
172
- shopify.on('exit', () => {
173
- cleanup();
174
- });
175
-
176
- return { shopify, ...assets, cleanup };
338
+ return { cleanup };
177
339
  }
178
340
 
179
341
  export function lintAll({ cwd = process.cwd() } = {}) {
@@ -187,7 +349,7 @@ export function lintAll({ cwd = process.cwd() } = {}) {
187
349
  ['./assets/*.css', '--config', '.config/.stylelintrc.json'],
188
350
  { name: 'stylelint', cwd }
189
351
  );
190
- const themeCheck = runShopify(['theme', 'check'], { cwd, name: 'theme-check' });
352
+ const themeCheck = runThemeCheckFiltered({ cwd });
191
353
  return { eslint, stylelint, themeCheck };
192
354
  }
193
355
 
@@ -1,15 +1,23 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import pc from 'picocolors';
3
3
 
4
- function spawnInherit(cmd, args, { cwd = process.cwd(), name = cmd } = {}) {
4
+ function spawnShopify(
5
+ cmd,
6
+ args,
7
+ { cwd = process.cwd(), name = cmd, stdio = 'inherit', onStdout, onStderr } = {}
8
+ ) {
5
9
  const child = spawn(cmd, args, {
6
10
  cwd,
7
- stdio: 'inherit',
11
+ stdio,
8
12
  shell: process.platform === 'win32',
9
13
  });
14
+ if (stdio === 'pipe') {
15
+ if (child.stdout && onStdout) child.stdout.on('data', (buf) => onStdout(String(buf)));
16
+ if (child.stderr && onStderr) child.stderr.on('data', (buf) => onStderr(String(buf)));
17
+ }
10
18
  child.on('exit', (code) => {
11
19
  if (code && code !== 0) {
12
- console.log(pc.red(`\n ${name} exited with code ${code}\n`));
20
+ console.log(pc.red(`\n [${name}] exited with code ${code}\n`));
13
21
  }
14
22
  });
15
23
  return child;
@@ -19,11 +27,17 @@ function spawnInherit(cmd, args, { cwd = process.cwd(), name = cmd } = {}) {
19
27
  * Run Shopify CLI, falling back to npx when `shopify` isn't available.
20
28
  * @param {string[]} args e.g. ['theme','check']
21
29
  */
22
- export function runShopify(args, { cwd = process.cwd(), name = 'shopify' } = {}) {
23
- const child = spawnInherit('shopify', args, { cwd, name });
30
+ export function runShopify(args, { cwd = process.cwd(), name = 'shopify', stdio = 'inherit', onStdout, onStderr } = {}) {
31
+ const child = spawnShopify('shopify', args, { cwd, name, stdio, onStdout, onStderr });
24
32
  child.on('error', (err) => {
25
33
  if (err?.code !== 'ENOENT') return;
26
- spawnInherit('npx', ['-y', '@shopify/cli@latest', ...args], { cwd, name: 'shopify(npx)' });
34
+ spawnShopify('npx', ['-y', '@shopify/cli@latest', ...args], {
35
+ cwd,
36
+ name: 'shopify(npx)',
37
+ stdio,
38
+ onStdout,
39
+ onStderr,
40
+ });
27
41
  });
28
42
  return child;
29
43
  }
@@ -66,43 +66,20 @@ const VSCODE_TASKS_CONTENT = `{
66
66
  "version": "2.0.0",
67
67
  "tasks": [
68
68
  {
69
- "label": "Shopify",
69
+ "label": "Climaybe Serve",
70
70
  "type": "shell",
71
- "command": "climaybe serve:shopify",
71
+ "command": "climaybe serve",
72
72
  "isBackground": true,
73
73
  "presentation": {
74
74
  "echo": true,
75
75
  "reveal": "always",
76
76
  "focus": true,
77
- "panel": "new",
77
+ "panel": "shared",
78
78
  "group": "develop",
79
79
  "showReuseMessage": false,
80
80
  "clear": true
81
81
  },
82
82
  "problemMatcher": []
83
- },
84
- {
85
- "label": "Tailwind",
86
- "type": "shell",
87
- "command": "climaybe serve:assets",
88
- "isBackground": true,
89
- "presentation": {
90
- "echo": true,
91
- "reveal": "always",
92
- "focus": false,
93
- "panel": "new",
94
- "group": "develop",
95
- "showReuseMessage": false,
96
- "clear": true
97
- },
98
- "problemMatcher": []
99
- },
100
- {
101
- "label": "Run Both Consoles",
102
- "dependsOn": ["Shopify", "Tailwind"],
103
- "runOptions": {
104
- "runOn": "folderOpen"
105
- }
106
83
  }
107
84
  ]
108
85
  }
@@ -111,6 +88,8 @@ const VSCODE_TASKS_CONTENT = `{
111
88
  const GITIGNORE_BLOCK = `# climaybe: theme dev kit (managed)
112
89
  .vscode
113
90
  node_modules/
91
+ .DS_Store
92
+ **/.DS_Store
114
93
  assets/style.css
115
94
  assets/index.js
116
95
  .shopify
@@ -57,7 +57,9 @@ export function resolveInstallScope({ packageDir, cwd = process.cwd() } = {}) {
57
57
  return 'global';
58
58
  }
59
59
 
60
- function getLocalInstallFlag({ packageName, cwd = process.cwd() } = {}) {
60
+ export function getLocalInstallFlag({ packageName, cwd = process.cwd() } = {}) {
61
+ // Always keep climaybe in runtime dependencies for theme repos.
62
+ if (packageName === 'climaybe') return '--save';
61
63
  try {
62
64
  const pkgPath = join(cwd, 'package.json');
63
65
  if (!existsSync(pkgPath)) return '--save-dev';
@@ -88,6 +90,7 @@ export async function maybeOfferCliUpdate({
88
90
  timeoutMs = DEFAULT_TIMEOUT_MS,
89
91
  } = {}) {
90
92
  if (!packageName || !currentVersion || !canPromptForUpdate()) return;
93
+ const invocationCwd = process.cwd();
91
94
 
92
95
  const latestVersion = await fetchLatestVersion(packageName, timeoutMs);
93
96
  if (!latestVersion || !isVersionGreater(latestVersion, currentVersion)) return;
@@ -104,7 +107,8 @@ export async function maybeOfferCliUpdate({
104
107
  try {
105
108
  const updatedScope = runUpdate(packageName, { packageDir, cwd: process.cwd() });
106
109
  console.log(pc.green(`Updated ${packageName} (${updatedScope}) to latest. Restarting command...`));
107
- process.chdir(packageDir || process.cwd());
110
+ // Continue in the repo where the user invoked the command.
111
+ process.chdir(invocationCwd);
108
112
  } catch (err) {
109
113
  console.log(pc.red('Update failed. Continuing with current version.'));
110
114
  if (err?.message) console.log(pc.dim(err.message));