climaybe 3.0.6 → 3.0.8
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 +8 -2
- package/bin/version.txt +1 -1
- package/package.json +1 -1
- package/src/commands/build-scripts.js +5 -3
- package/src/commands/ensure-branches.js +7 -5
- package/src/commands/init.js +0 -2
- package/src/index.js +11 -7
- package/src/lib/build-scripts.js +39 -17
- package/src/lib/dev-runtime.js +2 -2
- package/src/workflows/build/build-scripts.js +0 -7
- package/src/workflows/build/reusable-build.yml +21 -2
- package/src/workflows/multi/multistore-hotfix-to-main.yml +4 -3
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ If no alias is given, syncs to the default store.
|
|
|
108
108
|
|
|
109
109
|
### `climaybe ensure-branches` / `climaybe theme ensure-branches`
|
|
110
110
|
|
|
111
|
-
Create missing `staging`
|
|
111
|
+
Create missing branches from your current branch (usually `main`). In single-store mode, this creates `staging` only. In multi-store mode, this creates `staging` plus per-store branches (`staging-<alias>`, `live-<alias>`). Use when the repo only has `main` (e.g. after a fresh clone) so the configured sync flow can run.
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
114
|
npx climaybe ensure-branches
|
|
@@ -242,8 +242,11 @@ Enabled via `climaybe init` prompt (`Enable build + Lighthouse workflows?`; defa
|
|
|
242
242
|
When enabled, builds are **resilient**:
|
|
243
243
|
- If `_scripts/*.js` or `_styles/main.css` are missing, the build workflow **skips** those steps and continues.
|
|
244
244
|
- `init` may offer to create entrypoints; **default answer is No**.
|
|
245
|
+
- Script bundling preserves comments/spacing and emits bundles only for root entry files (files imported by other top-level `_scripts/*.js` are inlined, not emitted separately).
|
|
246
|
+
- On `live-<alias>` branches only, script bundles are minified and overwrite `assets/*.js`; `main` and `staging-*` keep readable built JS.
|
|
247
|
+
- Live minified `assets/*` changes are intentionally excluded from hotfix backports to `main` (no branch-specific `.gitignore` split required).
|
|
245
248
|
|
|
246
|
-
Build workflows
|
|
249
|
+
Build workflows install deps with `npm ci` and run `npx --no-install climaybe build-scripts` plus `npx --no-install climaybe build`, so CI uses lockfile-pinned versions (no `@latest` drift).
|
|
247
250
|
|
|
248
251
|
| Workflow | Trigger | What it does |
|
|
249
252
|
|----------|---------|-------------|
|
|
@@ -258,6 +261,9 @@ dev config defaults (`.theme-check.yml`, `.shopifyignore`, `.prettierrc`,
|
|
|
258
261
|
`.lighthouserc.js`), writes `climaybe.config.json`, appends a managed `.gitignore` block, and optionally adds
|
|
259
262
|
`.vscode/tasks.json` (default: yes) wired to run `climaybe` dev commands.
|
|
260
263
|
|
|
264
|
+
Local serve commands keep Theme Check disabled by default for faster startup. Enable it explicitly with
|
|
265
|
+
`climaybe serve --theme-check` or `climaybe serve:assets --theme-check`.
|
|
266
|
+
|
|
261
267
|
You can create optional build entrypoints later with:
|
|
262
268
|
|
|
263
269
|
`climaybe create-entrypoints`
|
package/bin/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.0.
|
|
1
|
+
3.0.8
|
package/package.json
CHANGED
|
@@ -2,19 +2,21 @@ import pc from 'picocolors';
|
|
|
2
2
|
import { requireThemeProject } from '../lib/theme-guard.js';
|
|
3
3
|
import { buildScripts } from '../lib/build-scripts.js';
|
|
4
4
|
|
|
5
|
-
export async function buildScriptsCommand() {
|
|
5
|
+
export async function buildScriptsCommand(opts = {}) {
|
|
6
6
|
console.log(pc.bold('\n climaybe — Build scripts\n'));
|
|
7
7
|
if (!requireThemeProject()) return;
|
|
8
8
|
|
|
9
9
|
try {
|
|
10
|
+
const minify = opts.minify === true;
|
|
10
11
|
if (global.gc) global.gc();
|
|
11
|
-
const { bundles } = buildScripts({ cwd: process.cwd() });
|
|
12
|
+
const { bundles } = buildScripts({ cwd: process.cwd(), minify });
|
|
12
13
|
if (!bundles || bundles.length === 0) {
|
|
13
14
|
console.log(pc.yellow(' No _scripts/*.js entrypoints found; nothing to build.'));
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
const totalFiles = bundles.reduce((sum, b) => sum + (b.fileCount || 0), 0);
|
|
17
|
-
|
|
18
|
+
const mode = minify ? 'minified' : 'readable';
|
|
19
|
+
console.log(pc.green(` Scripts built (${bundles.length} bundle(s), ${totalFiles} files total, ${mode})`));
|
|
18
20
|
for (const b of bundles) {
|
|
19
21
|
console.log(pc.dim(` - ${b.entryFile} → ${b.outputPath}`));
|
|
20
22
|
}
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
} from '../lib/git.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Create missing staging
|
|
15
|
-
* Use when the repo only has main (e.g. after clone) so the
|
|
14
|
+
* Create missing staging (single-store) or staging + per-store branches (multi-store) from current HEAD.
|
|
15
|
+
* Use when the repo only has main (e.g. after clone) so the mode-appropriate sync flow can run.
|
|
16
16
|
*/
|
|
17
17
|
export async function ensureBranchesCommand() {
|
|
18
18
|
console.log(pc.bold('\n climaybe — Ensure Branches\n'));
|
|
@@ -39,9 +39,11 @@ export async function ensureBranchesCommand() {
|
|
|
39
39
|
|
|
40
40
|
ensureStagingBranch();
|
|
41
41
|
const branchesToPush = ['staging'];
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
if (mode === 'multi') {
|
|
43
|
+
for (const alias of aliases) {
|
|
44
|
+
createStoreBranches(alias);
|
|
45
|
+
branchesToPush.push(`staging-${alias}`, `live-${alias}`);
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
console.log(pc.bold(pc.green('\n Branches ensured.\n')));
|
package/src/commands/init.js
CHANGED
|
@@ -125,7 +125,6 @@ async function runInitFlow() {
|
|
|
125
125
|
// 5. Create branches
|
|
126
126
|
if (mode === 'single') {
|
|
127
127
|
ensureStagingBranch();
|
|
128
|
-
createStoreBranches(stores[0].alias);
|
|
129
128
|
} else {
|
|
130
129
|
// Multi-store: staging branch + per-store branches
|
|
131
130
|
ensureStagingBranch();
|
|
@@ -181,7 +180,6 @@ async function runInitFlow() {
|
|
|
181
180
|
|
|
182
181
|
if (mode === 'single') {
|
|
183
182
|
console.log(pc.dim(' Branches: main, staging'));
|
|
184
|
-
console.log(pc.dim(` staging-${stores[0].alias}, live-${stores[0].alias}`));
|
|
185
183
|
console.log(pc.dim(' Workflow: staging → main with versioning + nightly hotfix tagging'));
|
|
186
184
|
} else {
|
|
187
185
|
console.log(pc.dim(' Branches: main, staging'));
|
package/src/index.js
CHANGED
|
@@ -61,20 +61,24 @@ function registerThemeCommands(cmd) {
|
|
|
61
61
|
|
|
62
62
|
cmd
|
|
63
63
|
.command('serve')
|
|
64
|
-
.description('Run local theme dev (Shopify + assets
|
|
65
|
-
.option('--
|
|
66
|
-
.action((opts) => serveAll({ includeThemeCheck: opts.themeCheck
|
|
64
|
+
.description('Run local theme dev (Shopify + assets; Theme Check off by default)')
|
|
65
|
+
.option('--theme-check', 'Enable Theme Check watcher')
|
|
66
|
+
.action((opts) => serveAll({ includeThemeCheck: opts.themeCheck === true }));
|
|
67
67
|
cmd.command('serve:shopify').description('Run Shopify theme dev server').action(() => serveShopify());
|
|
68
68
|
cmd
|
|
69
69
|
.command('serve:assets')
|
|
70
|
-
.description('Run assets watch (Tailwind + scripts
|
|
71
|
-
.option('--
|
|
72
|
-
.action((opts) => serveAssets({ includeThemeCheck: opts.themeCheck
|
|
70
|
+
.description('Run assets watch (Tailwind + scripts; Theme Check off by default)')
|
|
71
|
+
.option('--theme-check', 'Enable Theme Check watcher')
|
|
72
|
+
.action((opts) => serveAssets({ includeThemeCheck: opts.themeCheck === true }));
|
|
73
73
|
|
|
74
74
|
cmd.command('lint').description('Run theme linting (liquid, js, css)').action(() => lintAll());
|
|
75
75
|
|
|
76
76
|
cmd.command('build').description('Build assets (Tailwind + scripts build)').action(() => buildAll());
|
|
77
|
-
cmd
|
|
77
|
+
cmd
|
|
78
|
+
.command('build-scripts')
|
|
79
|
+
.description('Build _scripts → assets/index.js')
|
|
80
|
+
.option('--minify', 'Minify output bundles')
|
|
81
|
+
.action(buildScriptsCommand);
|
|
78
82
|
cmd
|
|
79
83
|
.command('create-entrypoints')
|
|
80
84
|
.description('Create _scripts/main.js and _styles/main.css (optional)')
|
package/src/lib/build-scripts.js
CHANGED
|
@@ -41,7 +41,24 @@ function stripModuleSyntax(content) {
|
|
|
41
41
|
return cleaned;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function
|
|
44
|
+
function minifyScriptContent(content) {
|
|
45
|
+
let minified = content;
|
|
46
|
+
// Strip block comments first so line-level processing is simpler.
|
|
47
|
+
minified = minified.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
48
|
+
// Strip line comments and trim lines.
|
|
49
|
+
minified = minified
|
|
50
|
+
.split('\n')
|
|
51
|
+
.map((line) => line.replace(/\/\/.*$/g, '').trim())
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.join('\n');
|
|
54
|
+
// Collapse excessive whitespace around common tokens.
|
|
55
|
+
minified = minified.replace(/\s*([{}();,:=+\-*/<>[\]])\s*/g, '$1');
|
|
56
|
+
// Keep one space where token concatenation could break identifiers.
|
|
57
|
+
minified = minified.replace(/\b(const|let|var|function|class|return|if|for|while|switch|case|new)\s+/g, '$1 ');
|
|
58
|
+
return minified;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function processScriptFile({ scriptsDir, filePath, processedFiles, minify = false }) {
|
|
45
62
|
if (processedFiles.has(filePath)) return '';
|
|
46
63
|
processedFiles.add(filePath);
|
|
47
64
|
|
|
@@ -61,17 +78,11 @@ function processScriptFile({ scriptsDir, filePath, processedFiles }) {
|
|
|
61
78
|
for (const importPath of imports) {
|
|
62
79
|
const resolvedImport = resolveImportPath(filePath, importPath);
|
|
63
80
|
if (!resolvedImport) continue;
|
|
64
|
-
importedContent += processScriptFile({ scriptsDir, filePath: resolvedImport, processedFiles });
|
|
81
|
+
importedContent += processScriptFile({ scriptsDir, filePath: resolvedImport, processedFiles, minify });
|
|
65
82
|
}
|
|
66
83
|
|
|
67
84
|
content = stripModuleSyntax(content);
|
|
68
|
-
|
|
69
|
-
if (process.env.NODE_ENV === 'production') {
|
|
70
|
-
content = content.replace(/\/\*\*[\s\S]*?\*\//g, '');
|
|
71
|
-
content = content.replace(/^\s*\*.*$/gm, '');
|
|
72
|
-
content = content.replace(/console\.(log|warn|error)\([^)]*\);?\s*/g, '');
|
|
73
|
-
content = content.replace(/^\s*\n/gm, '');
|
|
74
|
-
}
|
|
85
|
+
if (minify) content = minifyScriptContent(content);
|
|
75
86
|
|
|
76
87
|
return importedContent + '\n' + content;
|
|
77
88
|
}
|
|
@@ -115,7 +126,7 @@ function outputNameForEntrypoint(entryFile) {
|
|
|
115
126
|
return basename(entryFile);
|
|
116
127
|
}
|
|
117
128
|
|
|
118
|
-
function buildSingleEntrypoint({ cwd, entryFile }) {
|
|
129
|
+
function buildSingleEntrypoint({ cwd, entryFile, minify = false }) {
|
|
119
130
|
const scriptsDir = join(cwd, '_scripts');
|
|
120
131
|
const entryPath = join(scriptsDir, entryFile);
|
|
121
132
|
if (!existsSync(entryPath)) {
|
|
@@ -123,8 +134,9 @@ function buildSingleEntrypoint({ cwd, entryFile }) {
|
|
|
123
134
|
}
|
|
124
135
|
|
|
125
136
|
const processedFiles = new Set();
|
|
126
|
-
let finalContent = processScriptFile({ scriptsDir, filePath: entryFile, processedFiles });
|
|
137
|
+
let finalContent = processScriptFile({ scriptsDir, filePath: entryFile, processedFiles, minify });
|
|
127
138
|
finalContent = stripModuleSyntax(finalContent);
|
|
139
|
+
if (minify) finalContent = minifyScriptContent(finalContent);
|
|
128
140
|
|
|
129
141
|
const assetsDir = join(cwd, 'assets');
|
|
130
142
|
mkdirSync(assetsDir, { recursive: true });
|
|
@@ -136,18 +148,28 @@ function buildSingleEntrypoint({ cwd, entryFile }) {
|
|
|
136
148
|
return { entryFile, fileCount: processedFiles.size, outputPath };
|
|
137
149
|
}
|
|
138
150
|
|
|
139
|
-
export function buildScripts({ cwd = process.cwd(), entry = null } = {}) {
|
|
151
|
+
export function buildScripts({ cwd = process.cwd(), entry = null, minify = false } = {}) {
|
|
140
152
|
const scriptsDir = join(cwd, '_scripts');
|
|
141
153
|
let entrypoints = entry ? [entry.endsWith('.js') ? entry : `${entry}.js`] : listTopLevelEntrypoints(scriptsDir);
|
|
142
|
-
if (!entry && entrypoints.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
if (!entry && entrypoints.length > 1) {
|
|
155
|
+
// Emit only root top-level scripts. If one top-level file is imported by another
|
|
156
|
+
// top-level file, it is bundled into the importer and should not be emitted alone.
|
|
157
|
+
const importedByTopLevel = new Set();
|
|
158
|
+
for (const ep of entrypoints) {
|
|
159
|
+
const imported = collectImportedFiles({ scriptsDir, entryFile: ep });
|
|
160
|
+
imported.delete(ep);
|
|
161
|
+
for (const file of imported) importedByTopLevel.add(file);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const rootEntrypoints = entrypoints.filter((ep) => !importedByTopLevel.has(ep));
|
|
165
|
+
if (rootEntrypoints.length > 0) {
|
|
166
|
+
entrypoints = rootEntrypoints;
|
|
167
|
+
}
|
|
146
168
|
}
|
|
147
169
|
if (entrypoints.length === 0) {
|
|
148
170
|
return { bundles: [] };
|
|
149
171
|
}
|
|
150
|
-
const bundles = entrypoints.map((entryFile) => buildSingleEntrypoint({ cwd, entryFile }));
|
|
172
|
+
const bundles = entrypoints.map((entryFile) => buildSingleEntrypoint({ cwd, entryFile, minify }));
|
|
151
173
|
return { bundles };
|
|
152
174
|
}
|
|
153
175
|
|
package/src/lib/dev-runtime.js
CHANGED
|
@@ -217,7 +217,7 @@ export function serveShopify({ cwd = process.cwd() } = {}) {
|
|
|
217
217
|
return runShopify(args, { cwd, name: 'shopify' });
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
export function serveAssets({ cwd = process.cwd(), includeThemeCheck =
|
|
220
|
+
export function serveAssets({ cwd = process.cwd(), includeThemeCheck = false } = {}) {
|
|
221
221
|
printServeStartupHeader();
|
|
222
222
|
const env = { ...process.env, NODE_ENV: 'production' };
|
|
223
223
|
const styleEntrypoint = join(cwd, '_styles', 'main.css');
|
|
@@ -312,7 +312,7 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = true } =
|
|
|
312
312
|
return { tailwind, devMcp, scriptsWatch, themeCheckWatch, cleanup };
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
export function serveAll({ cwd = process.cwd(), includeThemeCheck =
|
|
315
|
+
export function serveAll({ cwd = process.cwd(), includeThemeCheck = false } = {}) {
|
|
316
316
|
// Start assets first, then bring up Shopify after a short delay.
|
|
317
317
|
const assets = serveAssets({ cwd, includeThemeCheck });
|
|
318
318
|
let shopify = null;
|
|
@@ -67,13 +67,6 @@ function processScriptFile(filePath, processedFiles = new Set()) {
|
|
|
67
67
|
|
|
68
68
|
content = stripModuleSyntax(content);
|
|
69
69
|
|
|
70
|
-
if (process.env.NODE_ENV === 'production') {
|
|
71
|
-
content = content.replace(/\/\*\*[\s\S]*?\*\//g, '');
|
|
72
|
-
content = content.replace(/^\s*\*.*$/gm, '');
|
|
73
|
-
content = content.replace(/console\.(log|warn|error)\([^)]*\);?\s*/g, '');
|
|
74
|
-
content = content.replace(/^\s*\n/gm, '');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
70
|
return importedContent + '\n' + content;
|
|
78
71
|
}
|
|
79
72
|
|
|
@@ -23,6 +23,14 @@ jobs:
|
|
|
23
23
|
with:
|
|
24
24
|
node-version: "24"
|
|
25
25
|
|
|
26
|
+
- name: Install dependencies from lockfile
|
|
27
|
+
run: |
|
|
28
|
+
if [ ! -f "package-lock.json" ]; then
|
|
29
|
+
echo "package-lock.json is required for deterministic build workflow installs."
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
npm ci
|
|
33
|
+
|
|
26
34
|
- name: Detect build entrypoints
|
|
27
35
|
id: detect
|
|
28
36
|
run: |
|
|
@@ -36,16 +44,27 @@ jobs:
|
|
|
36
44
|
fi
|
|
37
45
|
echo "has_scripts=$HAS_SCRIPTS" >> $GITHUB_OUTPUT
|
|
38
46
|
echo "has_styles=$HAS_STYLES" >> $GITHUB_OUTPUT
|
|
47
|
+
BRANCH_NAME="${{ github.head_ref || github.ref_name }}"
|
|
48
|
+
if [[ "$BRANCH_NAME" == live-* ]]; then
|
|
49
|
+
echo "scripts_minify=true" >> $GITHUB_OUTPUT
|
|
50
|
+
else
|
|
51
|
+
echo "scripts_minify=false" >> $GITHUB_OUTPUT
|
|
52
|
+
fi
|
|
39
53
|
|
|
40
54
|
- name: Build scripts
|
|
41
55
|
if: steps.detect.outputs.has_scripts == 'true'
|
|
42
|
-
run:
|
|
56
|
+
run: |
|
|
57
|
+
if [ "${{ steps.detect.outputs.scripts_minify }}" = "true" ]; then
|
|
58
|
+
npx --no-install climaybe build-scripts --minify
|
|
59
|
+
else
|
|
60
|
+
npx --no-install climaybe build-scripts
|
|
61
|
+
fi
|
|
43
62
|
|
|
44
63
|
- name: Run Tailwind build
|
|
45
64
|
id: build
|
|
46
65
|
run: |
|
|
47
66
|
if [ "${{ steps.detect.outputs.has_styles }}" = "true" ]; then
|
|
48
|
-
NODE_ENV=production npx -
|
|
67
|
+
NODE_ENV=production npx --no-install climaybe build
|
|
49
68
|
else
|
|
50
69
|
echo "No _styles/main.css found; skipping Tailwind build."
|
|
51
70
|
fi
|
|
@@ -92,11 +92,12 @@ jobs:
|
|
|
92
92
|
exit 0
|
|
93
93
|
fi
|
|
94
94
|
|
|
95
|
-
# Default store: commits outside stores/
|
|
95
|
+
# Default store: commits outside stores/ and assets/ trigger backport.
|
|
96
|
+
# Live-branch minified assets should never backport into main.
|
|
96
97
|
# Non-default store: only commits under stores/<alias>/ trigger backport; root JSON must not overwrite main.
|
|
97
98
|
if [ -n "$DEFAULT_ALIAS" ] && [ "$SOURCE_ALIAS" = "$DEFAULT_ALIAS" ]; then
|
|
98
|
-
COMMITS=$(git log --oneline ${MERGE_BASE}..origin/$SOURCE -- . ':!stores/' 2>/dev/null | \
|
|
99
|
-
grep -v "\[stores-to-root\]" | grep -v "\[root-to-stores\]" | grep -v "chore(release)" || true)
|
|
99
|
+
COMMITS=$(git log --oneline ${MERGE_BASE}..origin/$SOURCE -- . ':!stores/' ':!assets/' 2>/dev/null | \
|
|
100
|
+
grep -v "\[stores-to-root\]" | grep -v "\[root-to-stores\]" | grep -v "chore(release)" | grep -v "chore(assets)" || true)
|
|
100
101
|
else
|
|
101
102
|
COMMITS=$(git log --oneline ${MERGE_BASE}..origin/$SOURCE -- "stores/${SOURCE_ALIAS}/" 2>/dev/null | \
|
|
102
103
|
grep -v "\[stores-to-root\]" | grep -v "\[root-to-stores\]" | grep -v "chore(release)" || true)
|