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 +11 -5
- package/bin/version.txt +1 -1
- package/package.json +1 -1
- package/src/commands/update-workflows.js +31 -4
- package/src/index.js +5 -4
- package/src/lib/dev-runtime.js +182 -20
- package/src/lib/shopify-cli.js +20 -6
- package/src/lib/theme-dev-kit.js +5 -26
- package/src/lib/update-notifier.js +6 -2
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
|
|
118
|
+
### `climaybe update` / `climaybe theme update`
|
|
119
119
|
|
|
120
|
-
Refresh
|
|
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
|
|
129
|
+
npx climaybe update
|
|
124
130
|
```
|
|
125
131
|
|
|
126
|
-
|
|
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
|
|
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.
|
|
1
|
+
3.0.4
|
package/package.json
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
|
-
import {
|
|
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
|
|
7
|
-
console.log(pc.bold('\n climaybe — Update
|
|
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
|
|
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 {
|
|
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
|
|
85
|
-
.
|
|
86
|
-
.
|
|
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')
|
package/src/lib/dev-runtime.js
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
-
|
|
58
|
+
writeTaggedLine(tag || name, color, `exited with signal ${signal}`);
|
|
30
59
|
return;
|
|
31
60
|
}
|
|
32
61
|
if (code && code !== 0) {
|
|
33
|
-
|
|
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
|
-
|
|
240
|
+
writeTaggedLine('scripts', pc.yellow, 'built (initial)');
|
|
89
241
|
} catch (err) {
|
|
90
|
-
|
|
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
|
-
|
|
255
|
+
writeTaggedLine('scripts', pc.yellow, 'rebuilt');
|
|
102
256
|
} catch (err) {
|
|
103
|
-
|
|
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 =
|
|
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
|
-
//
|
|
316
|
+
// Start assets first, then bring up Shopify after a short delay.
|
|
160
317
|
const assets = serveAssets({ cwd, includeThemeCheck });
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
352
|
+
const themeCheck = runThemeCheckFiltered({ cwd });
|
|
191
353
|
return { eslint, stylelint, themeCheck };
|
|
192
354
|
}
|
|
193
355
|
|
package/src/lib/shopify-cli.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
3
|
|
|
4
|
-
function
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
}
|
package/src/lib/theme-dev-kit.js
CHANGED
|
@@ -66,43 +66,20 @@ const VSCODE_TASKS_CONTENT = `{
|
|
|
66
66
|
"version": "2.0.0",
|
|
67
67
|
"tasks": [
|
|
68
68
|
{
|
|
69
|
-
"label": "
|
|
69
|
+
"label": "Climaybe Serve",
|
|
70
70
|
"type": "shell",
|
|
71
|
-
"command": "climaybe serve
|
|
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": "
|
|
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
|
-
|
|
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));
|