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 +1 -1
- package/package.json +5 -5
- package/src/commands/ensure-branches.js +4 -9
- package/src/commands/init.js +18 -1
- package/src/lib/dev-runtime.js +23 -1
- package/src/lib/theme-dev-kit.js +38 -5
- package/src/lib/update-notifier.js +38 -4
- package/src/workflows/build/reusable-build.yml +0 -1
package/bin/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.0.
|
|
1
|
+
3.0.2
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "climaybe",
|
|
3
|
-
"version": "3.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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -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'));
|
package/src/lib/dev-runtime.js
CHANGED
|
@@ -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
|
-
|
|
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);
|
package/src/lib/theme-dev-kit.js
CHANGED
|
@@ -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: :
|
|
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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
47
|
-
|
|
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
|
-
|
|
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.'));
|